package com.sp.business.normalization.impl.mf2;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Mf2ConfFileProcessor {
    // private static final Logger logger =
    // LoggerFactory.getLogger(Mf2ConfFileProcessor.class);
    
    private List<TreeNode> svcList;
    private List<TreeNode> svcGroupList;
    private List<TreeNode> networkObjList;
    private List<TreeNode> hostObjList;
    private List<TreeNode> usersAsHostList;
    
    private static Map<String, Boolean> protocolMap = new HashMap<String, Boolean>();
    static {
        protocolMap.put("ip", true);
        protocolMap.put("udp", true);
        protocolMap.put("tcp", true);
        protocolMap.put("icmp", true);
    }
    
    public Mf2ConfFileProcessor() {
        svcList = new ArrayList<TreeNode>();
        // services my have more than one port or port range so we build groups
        // for them.
        svcGroupList = new ArrayList<TreeNode>();
        
        // networkObjs, host, and users-as-host are referred to by index in the groupobject file:
        networkObjList = new ArrayList<TreeNode>();
        hostObjList = new ArrayList<TreeNode>();
        usersAsHostList = new ArrayList<TreeNode>();
    }

    public TreeNode processInterfaces(BufferedReader reader) throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        TreeNode root = new TreeNode(NodeType.INTERFACES, "interfaces");

        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            if ("EthernetIF".equals(child.label)) {
                String name = (String) Mf2Object.find(child, "name");
                TreeNode iface = new TreeNode(NodeType.INTERFACE, name);

                // set default values
                String ip = "0.0.0.0";
                Integer mask = 32;
                String zone = (String) Mf2Object.find(child, "zone");

                Mf2Object address = (Mf2Object) Mf2Object.find(child, "address:0");
                if (address != null) {
                    ip = (String) address.get("ip");
                    mask = (Integer) address.get("mask");
                }

                // build iface node
                iface.getChildren().add(new TreeNode(NodeType.INTERFACE_IP, ip));
                iface.getChildren().add(new TreeNode(NodeType.MASK, Integer.toString(mask)));
                iface.getChildren().add(new TreeNode(NodeType.ZONE, zone));
                root.getChildren().add(iface);
            }
            child = (Mf2Object) result.get(ptr++);
        }
        return root;
    }

    public TreeNode processRoutes(BufferedReader reader) throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        TreeNode root = new TreeNode(NodeType.ROUTES, "routes");

        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            // net: "11.4.9.0/24"
            String network = (String)child.get("net");
            String[] networkParts = network.split("/");
            String dest = networkParts[0];
            String mask = networkParts[1];
            
            // gateway
            String gateway = "0.0.0.0";
            Mf2Object routeObj = (Mf2Object)Mf2Object.find(child, "route:0");
            if(routeObj != null) {
                gateway = (String)routeObj.get("gw");
            }
            
            // build route node
            TreeNode route = new TreeNode(NodeType.ROUTE, "route");
            route.getChildren().add(new TreeNode(NodeType.ROUTE_DEST, dest));
            route.getChildren().add(new TreeNode(NodeType.MASK, mask));
            route.getChildren().add(new TreeNode(NodeType.ROUTE_GATEWAY, gateway));
            //route.getChildren().add(new TreeNode(NodeType.ROUTE_INTERFACE, "")); //iface not supplied
            root.getChildren().add(route);
            
            child = (Mf2Object) result.get(ptr++);
        }
        return root;
    }
    
    public TreeNode processZones(BufferedReader reader)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);

        TreeNode root = new TreeNode(NodeType.ZONES, "zones");
        return root;
    }

    public TreeNode processServices(BufferedReader reader)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        
        TreeNode root = new TreeNode(NodeType.SERVICES, "services");
        TreeNode any = new TreeNode(NodeType.SERVICE, "Any");
        any.getChildren().add(new TreeNode(NodeType.SERVICE_PROTOCOL, "ip"));
        any.getChildren().add(new TreeNode(NodeType.SERVICE_PORT, "0"));
        any.getChildren().add(new TreeNode(NodeType.SERVICE_PORT_TO, "65535"));
        root.getChildren().add(any);
        
        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            Integer id = (Integer)child.get("id");
            String name = (String)child.get("name");
            String rawPorts = (String)child.get("str_svc_port");
            
            // ensure valid protocol
            String protocol = (String)child.get("protocol");
            protocol = protocol.toUpperCase();
            if(!protocolMap.containsKey(protocol)) {
                protocol = "ip";
            }
            
            // ports are pathological: 6660-6670,7000
            List<String> svcNames = new ArrayList<String>();
            String[] ports = rawPorts.split(",");
            for(String port : ports) {
                // toPort is optional (only for port range)
                String toPort = null;
                if(port.contains("-")) {
                    String[] beginEnd = port.split("-");
                    port = beginEnd[0];
                    toPort = beginEnd[1];
                } else if("*".equals(port)) {
                    // handle any ports
                    port = "0";
                    toPort = "65535";
                }
                // if more than one port, append '_1111-2222' or whatever
                String svcName = ports.length > 1 ? name + "_" + port : name;
                // create service
                TreeNode service = new TreeNode(NodeType.SERVICE, svcName);
                service.getChildren().add(new TreeNode(NodeType.SERVICE_PROTOCOL, protocol));
                service.getChildren().add(new TreeNode(NodeType.SERVICE_PORT, port));
                if(toPort != null)
                {
                  service.getChildren().add(new TreeNode(NodeType.SERVICE_PORT_TO, toPort));
                }
                root.getChildren().add(service);
                // if this is a lone service save to be accessed later
                if(ports.length == 1) {
                    addToList(svcList, service, id);
                }
                // save the name for possible svc group
                svcNames.add(svcName);
            }
            // create a service group if there were more than port/port-range
            if(svcNames.size() > 1) {
                TreeNode serviceGroup = new TreeNode(NodeType.SERVICE_GROUP, name);
                for(String svcName : svcNames) {
                    serviceGroup.getChildren().add(new TreeNode(NodeType.LABEL, svcName));
                }
                // save this group to be accessed later 
                addToList(svcList, serviceGroup, Integer.valueOf(id));
                // save the group to be added when svc groups processed
                svcGroupList.add(serviceGroup);
            }
            child = (Mf2Object) result.get(ptr++);
        }
        return root;
    }
    
    public TreeNode processServiceGroups(BufferedReader reader)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        
        TreeNode root = new TreeNode(NodeType.SERVICE_GROUPS, "service-groups");
        // add service groups built for multi port/port-range services above
        root.getChildren().addAll(svcGroupList);
        
        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            // create group node
            String name = (String)child.get("name");
            TreeNode serviceGroup = new TreeNode(NodeType.SERVICE_GROUP, name);

            // iterate thru the services object
            Mf2Object services = (Mf2Object)child.get("services");
            int listIndex = 0;
            for(Object o : services.list) {
                if(o instanceof Boolean && (Boolean)o == true) {
                    // svcList is built in processServices()
                    TreeNode svcNode = svcList.get(listIndex);
                    // if it's a service, add it's name
                    if((NodeType.SERVICE).equals(svcNode.getNodeType())) {
                        serviceGroup.getChildren().add(new TreeNode(NodeType.LABEL, svcNode.getText()));
                    } else {
                        // else it's a group, so get all it's labels and add them
                        serviceGroup.getChildren().addAll(svcNode.getChildrenByType(NodeType.LABEL));
                    }
                }
                // services are a sparse array (in a Lua table)
                listIndex++;
            }
            root.getChildren().add(serviceGroup);
            child = (Mf2Object) result.get(ptr++);
        }
        return root;
    }
    
    public TreeNode processNetworkObjs(BufferedReader reader)
            throws IOException {
        return processNetworkObjs(reader, networkObjList);
    }
    
    public TreeNode processHostObjs(BufferedReader reader)
            throws IOException {
        return processNetworkObjs(reader, hostObjList);
    }
    
    public TreeNode processNetworkObjs(BufferedReader reader, List<TreeNode> list)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        TreeNode netObjs = new TreeNode(NodeType.NETWORK_OBJS, "network-objects");
        
        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            Integer id = (Integer)child.get("id");
            String name = (String)child.get("name");
            TreeNode netObj = new TreeNode(NodeType.NETWORK_OBJ, name);
            
            String zone = (String)child.get("zone");
            String ip = (String)child.get("ip");
            String mask = (String)child.get("mask");
            String rangestart = (String)child.get("rangestart");
            String rangeend = (String)child.get("rangeend");
            
            if(rangestart != null && rangeend != null) {
                netObj.getChildren().add(new TreeNode(NodeType.NETWORK_OBJ_IP, rangestart));
                netObj.getChildren().add(new TreeNode(NodeType.NETWORK_OBJ_TO, rangeend));
            } else {
                netObj.getChildren().add(new TreeNode(NodeType.NETWORK_OBJ_IP, ip));
                mask = mask == null ? "32" : mask;
                netObj.getChildren().add(new TreeNode(NodeType.MASK, mask));
            }
            netObj.getChildren().add(new TreeNode(NodeType.ZONE, zone));
            netObjs.getChildren().add(netObj);
            addToList(list, netObj, id);
            
            child = (Mf2Object) result.get(ptr++);
        }
        return netObjs;
    }
    
    public TreeNode processUsersAsHost(BufferedReader reader) throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        TreeNode netObjs = new TreeNode(NodeType.NETWORK_OBJS, "network-objects");
        
        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            String name = (String)child.get("name");
            String ip = (String)Mf2Object.find(child, "ip:0");
            String mask = "32";
            
            TreeNode netObj = new TreeNode(NodeType.NETWORK_OBJ, name);
            netObj.getChildren().add(new TreeNode(NodeType.NETWORK_OBJ_IP, ip));
            netObj.getChildren().add(new TreeNode(NodeType.MASK, mask));
            netObjs.getChildren().add(netObj);
            
            usersAsHostList.add(netObj);
            child = (Mf2Object) result.get(ptr++);
        }
        
        return netObjs;
    }
    
    /**
     * NetworkObjs, host, and users-as-host are referred to by index in the groupobject file.
     * The 'arrays' are lua tables ie sparse. Mf2Object attempts to emulate the behavior of
     * Lua tables.
     * 
     * Example:
     * 
     * hosts={[2]=true,[3]=true},
     * networks={[2]=true,[4]=true},
     * users={}
     * 
     * @param reader
     * @return
     * @throws IOException
     */
    public TreeNode processNetworkObjGroups(BufferedReader reader)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        TreeNode root = new TreeNode(NodeType.NETWORK_OBJ_GROUPS, "network-objects-groups");

        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            String name = (String)child.get("name");
            TreeNode netObjGrp = new TreeNode(NodeType.NETWORK_OBJ, name);
            
            Mf2Object networks = (Mf2Object)child.get("networks");
            int networksPtr = 0;
            for(Object o : networks.list) {
                if(o != null && o instanceof Boolean && (Boolean)o) {
                    TreeNode networkNode = networkObjList.get(networksPtr);
                    netObjGrp.getChildren().add(new TreeNode(NodeType.LABEL, networkNode.getText()));
                }
                networksPtr++;
            }
            
            Mf2Object hosts = (Mf2Object)child.get("hosts");
            int hostsPtr = 0;
            for(Object o : hosts.list) {
                if(o != null && o instanceof Boolean && (Boolean)o) {
                    TreeNode hostNode = hostObjList.get(hostsPtr);
                    netObjGrp.getChildren().add(new TreeNode(NodeType.LABEL, hostNode.getText()));
                }
                hostsPtr++;
            }
            
            Mf2Object users = (Mf2Object)child.get("users");
            int usersPtr = 0;
            for(Object o : users.list) {
                if(o != null && o instanceof Boolean && (Boolean)o) {
                    TreeNode userNode = usersAsHostList.get(usersPtr);
                    netObjGrp.getChildren().add(new TreeNode(NodeType.LABEL, userNode.getText()));
                }
                usersPtr++;
            }
            
            root.getChildren().add(netObjGrp);
            child = (Mf2Object) result.get(ptr++);
        }
        return root;
    }
    
    public TreeNode processUsers(BufferedReader reader)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        Mf2Object result = parser.parse(reader);
        //Mf2Object.printMf2Object(result, "");
        TreeNode root = new TreeNode(NodeType.USERS, "users");
        
        int ptr = 0;
        Mf2Object child = (Mf2Object) result.get(ptr++);
        while (child != null) {
            String name = (String)child.get("name");
            root.getChildren().add(new TreeNode(NodeType.USER, name));
            
            child = (Mf2Object) result.get(ptr++);
        }
        return root;
    }
    
    public TreeNode processAdmins(BufferedReader reader)
            throws IOException {
        Mf2Parser parser = new Mf2Parser();
        List<Mf2Object> result = parser.parseObjList(reader);
        
        TreeNode root = new TreeNode(NodeType.USERS, "users");
        
        for(Mf2Object child : result) {
            String id = (String)child.get("id");
            if(id != null)
                root.getChildren().add(new TreeNode(NodeType.USER, id));
        }
        return root;
    }
    
    public TreeNode processPolicies(BufferedReader fwReader, BufferedReader natReader)
            throws IOException {
        
        // only one policy for all rules:
        TreeNode policies = new TreeNode(NodeType.POLICIES, "policies");
        TreeNode globalPolicy = new TreeNode(NodeType.POLICY, "global policy");
        // set as global policy
        globalPolicy.getChildren().add(new TreeNode(NodeType.GLOBAL, "Global Policy"));
        policies.getChildren().add(globalPolicy);
        
        Mf2Parser fwParser = new Mf2Parser();
        Mf2Object fwrules = fwParser.parse(fwReader);
        
        TreeNode securityRules = new TreeNode(NodeType.SECURITY_RULES, "security rules");
        Mf2Object rules = (Mf2Object)fwrules.get("rules");
        for(Object obj : rules.list) {
            TreeNode ruleNode = processSecurityRule((Mf2Object)obj);
            securityRules.getChildren().add(ruleNode);
        }
        
        // parse nat rules
        Mf2Parser natParser = new Mf2Parser();
        Mf2Object natrules = natParser.parse(natReader);
        //Mf2Object.printMf2Object(natrules, " ");
        TreeNode natRules = new TreeNode(NodeType.NAT_RULES, "nat rules");
        rules = (Mf2Object)Mf2Object.find(natrules, "localhost:rules");
        if(rules != null) {
            for(Object rule : rules.list) {
                TreeNode natNode = processNatRule((Mf2Object)rule);
                natRules.getChildren().add(natNode);
            }
        }
        
        globalPolicy.getChildren().add(securityRules);
        globalPolicy.getChildren().add(natRules);
        
        return policies;
    }
    
    private TreeNode processSecurityRule(Mf2Object ruleObj) {
        //Mf2Object.printMf2Object(ruleObj, "");
        Integer id = (Integer)ruleObj.get("rid");
        
        String enabled = "false";
        if("Y".equals((String)ruleObj.get("use")))
        {
          enabled = "true";
        }
        
        String action = "deny";
        if("allow".equals((String)ruleObj.get("action")))
        {
          action = "allow";
        }
        
        String logging = "false";
        if("Y".equals((String)ruleObj.get("log")))
        {
          logging = "true";
        }
        
        String description = (String)ruleObj.get("description");
        description = "NONE".equals(description) ? "" : description;
        
        TreeNode ruleNode = new TreeNode(NodeType.SECURITY_RULE, id.toString());
        ruleNode.getChildren().add(new TreeNode(NodeType.ENABLED, enabled));
        ruleNode.getChildren().add(new TreeNode(NodeType.ACTION, action));
        ruleNode.getChildren().add(new TreeNode(NodeType.LOGGING, logging));
        ruleNode.getChildren().add(new TreeNode(NodeType.DESCRIPTION, description));
        
        Mf2Object sources = (Mf2Object)ruleObj.get("from");
        // empty list mean Any
        if(sources.list.size() == 0) {
            sources.list.add("EH Any");
        }
        TreeNode sourceList = new TreeNode(NodeType.SOURCE_LIST, "sources");
        for(Object source : sources.list) {
            // strings are like: "EH Firemon_Admin-IP"
            String[] parts = ((String)source).split("\\s+");
            sourceList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        Mf2Object destinations = (Mf2Object)ruleObj.get("to");
        // empty list mean Any
        if(destinations.list.size() == 0) {
            destinations.list.add("EH Any");
        }
        TreeNode destinationList = new TreeNode(NodeType.DEST_LIST, "destinations");
        for(Object dest : destinations.list) {
            // strings are like: "EH Firemon_Admin-IP"
            String[] parts = ((String)dest).split("\\s+");
            destinationList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        Mf2Object services = (Mf2Object)ruleObj.get("service");
        // empty list mean Any
        if(services.list.size() == 0) {
            services.list.add("SS Any");
        }
        TreeNode serviceList = new TreeNode(NodeType.SERVICE_LIST, "rule services");
        for(Object service : services.list) {
            // strings are like: "SS TRACEROUTE-ICMP"
            String[] parts = ((String)service).split("\\s+");
            serviceList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        ruleNode.getChildren().add(sourceList);
        ruleNode.getChildren().add(destinationList);
        ruleNode.getChildren().add(serviceList);
        
        return ruleNode;
    }
    
    private TreeNode processNatRule(Mf2Object natObj) {
        String enabled = "false";
        if("Y".equals(natObj.get("use")))
        {
          enabled = "true";
        }
        
        String description = (String)natObj.get("d");
        
        Integer id = (Integer)natObj.get("rid");
        
        // original source list
        Mf2Object sourceObj = (Mf2Object)natObj.get("osrcaddr");
        if(sourceObj.list.size() == 0) {
            sourceObj.list.add("EH Any");
        }
        TreeNode sourceList = new TreeNode(NodeType.SOURCE_LIST, "source-list");
        for(Object o : sourceObj.list) {
            String[] parts = ((String)o).split("\\s+");
            sourceList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        // original dest list
        Mf2Object destObj = (Mf2Object)natObj.get("odstaddr");
        if(destObj.list.size() == 0) {
            destObj.list.add("EH Any");
        }
        TreeNode destList = new TreeNode(NodeType.DEST_LIST, "dest-list");
        for(Object o : destObj.list) {
            String[] parts = ((String)o).split("\\s+");
            destList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        // original service list
        Mf2Object svcObj = (Mf2Object)natObj.get("odstsvc");
        if(svcObj.list.size() == 0) {
            svcObj.list.add("SS Any");
        }
        TreeNode svcList = new TreeNode(NodeType.SERVICE_LIST, "service-list");
        for(Object o : svcObj.list) {
            String[] parts = ((String)o).split("\\s+");
            // TODO this can't be right:
            String label = "ORG".equals(parts[1]) ? "Any" : parts[1];
            svcList.getChildren().add(new TreeNode(NodeType.LABEL, label));
        }
        
        
        // translated source list
        Mf2Object transSrcObj = (Mf2Object)natObj.get("xsrcaddr");
        if(transSrcObj.list.size() == 0) {
            transSrcObj.list.add("EH Any");
        }
        TreeNode transSrcList = new TreeNode(NodeType.SOURCE_TRANSLATED_LIST, "source-translated-list");
        for(Object o : transSrcObj.list) {
            String[] parts = ((String)o).split("\\s+");
            transSrcList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        // translated dest list
        Mf2Object transDestObj = (Mf2Object)natObj.get("xdstaddr");
        if(transDestObj.list.size() == 0) {
            transDestObj.list.add("EH Any");
        }
        TreeNode transDestList = new TreeNode(NodeType.DEST_TRANSLATED_LIST, "dest-translated-list");
        for(Object o : transDestObj.list) {
            String[] parts = ((String)o).split("\\s+");
            transDestList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        // translated service list
        Mf2Object transSvcObj = (Mf2Object)natObj.get("xdstsvc");
        if(transSvcObj.list.size() == 0) {
            transSvcObj.list.add("EH Any");
        }
        TreeNode transSvcList = new TreeNode(NodeType.SERVICE_TRANSLATED_LIST, "service-translated-list");
        for(Object o : transSvcObj.list) {
            String[] parts = ((String)o).split("\\s+");
            transSvcList.getChildren().add(new TreeNode(NodeType.LABEL, parts[1]));
        }
        
        TreeNode natNode = new TreeNode(NodeType.NAT_RULE, id.toString());
        natNode.getChildren().add(new TreeNode(NodeType.ENABLED, enabled));
        natNode.getChildren().add(new TreeNode(NodeType.DESCRIPTION, description));
        natNode.getChildren().add(sourceList);
        natNode.getChildren().add(destList);
        natNode.getChildren().add(svcList);
        natNode.getChildren().add(transSrcList);
        natNode.getChildren().add(transDestList);
        natNode.getChildren().add(transSvcList);
        
        return natNode;
    }
    
    /*
     * Method to ensure list has enough elements to set arbitrary indexes
     */
    private static void addToList(List<TreeNode> list, TreeNode node, Integer id) {
        // ids are 1 based:
        id = id - 1;
        while(list.size() <= id) {
            list.add(null);
        }
        list.set(id, node);
    }
}
